Hint: Try executing this chunk by clicking the Run button within the chunk or by placing your cursor inside it and pressing Cmd+Shift+Enter.

# load required libraries

# to use harry potter dataset
# devtools::install_github("bradleyboehmke/harrypotter")
# devtools::install_github("quanteda/quanteda.sentiment")
# devtools::install_github("quanteda/quanteda.corpora")

library(quanteda)
library(readtext)
library(corpus)
library(tidyverse)
library(stringr)
library(tidytext)
library(harrypotter)
library(dplyr)
library(quanteda.sentiment)
library(vader)


require(quanteda)
require(quanteda.corpora)
require(quanteda.sentiment)
#library("quanteda", warn.conflicts = FALSE, quietly = TRUE)
afinn2 <- data_dictionary_AFINN

Harry Potter - Dataset

# load harry potter dataset 
titles <- c("Philosopher's Stone", "Chamber of Secrets", "Prisoner of Azkaban",
            "Goblet of Fire", "Order of the Phoenix", "Half-Blood Prince",
            "Deathly Hallows")

books <- list(philosophers_stone, chamber_of_secrets, prisoner_of_azkaban,
           goblet_of_fire, order_of_the_phoenix, half_blood_prince,
           deathly_hallows)
  
series <- tibble()

for(i in seq_along(titles)) {
        
        clean <- tibble(chapter = seq_along(books[[i]]),
                        text = books[[i]]) %>%
             #unnest_tokens(word, text) %>%
             mutate(book = titles[i]) %>%
             select(book, everything())

        series <- rbind(series, clean)
}

series$book <- factor(series$book, levels = rev(titles))

series
#book_groups <- series %>% group_by(book, chapter)
# tokenize hp1
#hp1_tokenized <- tokens_tolower(tokens(philosophers_stone, remove_punct = TRUE)) 

Harry Potter - AFINN Lexicon

Lexicoder: HP

# select only the "negative" and "positive" categories
#data_dictionary_LSD2015_pos_neg <- data_dictionary_LSD2015[1:2]
#hp1_lsd <- tokens_lookup(hp1_tokenized, dictionary = data_dictionary_LSD2015_pos_neg)

polarity(data_dictionary_LSD2015) <- 
  list(pos = c("positive", "neg_negative"), neg = c("negative", "neg_positive"))

hp1_lsd <- textstat_polarity(hp1_tokenized, data_dictionary_LSD2015)

hp1_lsd_tokens <- tokens_lookup(hp1_tokenized, data_dictionary_LSD2015, nested_scope = "dictionary", exclusive = FALSE)
hp1_lsd.df <- as.data.frame.matrix(hp1_lsd)
hp1_lsd.df$chapter <- 1:nrow(hp1_lsd.df)

plot <- ggplot(hp1_lsd, aes(x =hp1_lsd.df$chapter, y=sentiment)) +
          geom_bar(alpha = 0.8, stat = "identity", show.legend = FALSE)
plot + ylim(-1.0, 1.0) + labs(y="sentiment", x = "chapter") + ggtitle("HP1 - Lexicoder")

AFINN: HP

hp1_afinn2 <- textstat_valence(hp1_tokenized, afinn2, normalize="dictionary")

hp1_afinn2.df <- as.data.frame.matrix(hp1_afinn2)
hp1_afinn2.df$chapter <- 1:nrow(hp1_afinn2.df)

plot <- ggplot(hp1_afinn2.df, aes(x =hp1_afinn2.df$chapter, y=sentiment)) +
          geom_bar(alpha = 0.8, stat = "identity", show.legend = FALSE)
plot + ylim(-1.0, 1.0) + labs(y="sentiment", x = "chapter") + ggtitle("HP1 - AFINN")
Warnung: Use of `hp1_afinn2.df$chapter` is discouraged. Use `chapter` instead.

VADER: HP

QUANTEDA.SENTIMENT

AFINN: HP

# Work with quanteda.sentiment on HP corpus:
# convert tibble to dataframe
series.df <- as.data.frame(series)

# tokenize books
series_tokenized <- series.df %>%
  unnest_tokens(tokens, text)

# apply afinn lexicon
series_tokenized$afinn2 <- textstat_valence(series_tokenized$tokens, afinn2)$sentiment

# replace all 0 values with na
series_tokenized[series_tokenized == 0] <- NA

series_tokenized %>%
  group_by(book, chapter) %>% # group df by book and chapter to get sentiment per chapter
  summarise(sentiment = mean(afinn2, na.rm = TRUE)) %>% # calculate mean w/o regarding na values
  mutate(method = "AFINN") %>% # add column with method 
        ggplot(aes(chapter, sentiment, fill = book)) + # plot sentiment of books
          geom_bar(alpha = 0.8, stat = "identity", show.legend = FALSE) +
          facet_wrap(~ book, ncol = 2, scales = "free_x") +
          ggtitle("AFINN HP")
`summarise()` has grouped output by 'book'. You can override using the `.groups` argument.

Lexicoder: HP

# Work with quanteda.sentiment on HP corpus:
# apply lexicoder lexicon
series$lsd <- textstat_polarity(tokens(series$text), data_dictionary_LSD2015)$sentiment 

#series.df <- as.data.frame(series)

plot <- ggplot(series, aes(chapter, lsd, fill = book)) + # plot sentiment of books
          geom_bar(alpha = 0.8, stat = "identity", show.legend = FALSE) +
          facet_wrap(~ book, ncol = 2, scales = "free_x") +
          ggtitle("Lexicoder HP")
plot 

Vader: HP

REVIEWS DATASET

# load dataset
reviews <- readtext("datasets/goodreads_reviews_children_2.json", text_field = "review_text")

# convert to dataframe
reviews.df <- as.data.frame(reviews)

# add doc_id (i.e. according to index)
reviews.df$doc_id <- 1:nrow(reviews.df)

Sample Dataset

# get random sample of 50 reviews 
reviews_sample <- reviews.df[sample(1:nrow(reviews.df), 50,
   replace=FALSE),]

# get first 50 rows of data 
reviews_50 <- head(reviews.df,50)
reviews_50 = subset(reviews_50, select = c(doc_id,text,rating))

Get Translations of Dataset

# either via corpus 
reviews.corpus <- corpus(reviews)
docvars(reviews.corpus, "language") <- textcat(reviews.corpus)
reviews_en <- corpus_subset(reviews.corpus, language == "english", drop_docid = TRUE)

# or via dataframe logic
reviews.df$language <- textcat(reviews.df$text)

Sentiment Analysis on Reviews Dataset

AFINN

# Afinn
# tokenize 
reviews_tokenized <- reviews_50 %>%
  unnest_tokens(tokens, text)

# apply afinn lexicon
reviews_tokenized$afinn2 <- textstat_valence(reviews_tokenized$tokens, afinn2)$sentiment

# replace all 0 values with na
reviews_tokenized[reviews_tokenized == 0] <- NA

# calculate mean scores for tokens per doc
afinn_scores <- reviews_tokenized %>%
  group_by(doc_id) %>% # group df by doc_id to get mean sentiment score
  summarise(total = mean(afinn2, na.rm = TRUE)) #%>% # calculate mean w/o regarding na values

# add afinn scores to df 
reviews_50$afinn <- afinn_scores$total

# different version to get plot 
reviews_tokenized %>%
  group_by(doc_id) %>% # group df by book and chapter to get sentiment per chapter
  #reviews_tokenized$sentiment = mean(afinn2, na.rm = TRUE) %>%
  summarise(sentiment = mean(afinn2, na.rm = TRUE)) %>% # calculate mean w/o regarding na values
  mutate(method = "AFINN") %>% # add column with method 
        ggplot(aes(doc_id, sentiment, fill = doc_id)) + # plot sentiment of books
          geom_bar(alpha = 0.8, stat = "identity", show.legend = FALSE) +
          #facet_wrap(~ doc_id, ncol = 2, scales = "free_x") +
          ggtitle("AFINN Reviews")
Warnung: Removed 1 rows containing missing values (position_stack).

LEXICODER

# apply lexicoder lexicon
reviews_50$lsd <- textstat_polarity(tokens(reviews_50$text), data_dictionary_LSD2015)$sentiment 
#series.df <- as.data.frame(series)

plot <- ggplot(reviews_50, aes(doc_id, lsd, fill = doc_id)) + # plot sentiment of books
          geom_bar(alpha = 0.8, stat = "identity", show.legend = FALSE) +
          #facet_wrap(~ doc_id, ncol = 2, scales = "free_x") +
          ggtitle("Lexicoder Reviews")
plot 

Vader

reviews_50$vader <- vader_df(reviews_50$text)$compound

plot <- ggplot(reviews_50, aes(doc_id, vader, fill = doc_id)) + # plot sentiment of books
          geom_bar(alpha = 0.8, stat = "identity", show.legend = FALSE) +
          #facet_wrap(~ doc_id, ncol = 2, scales = "free_x") +
          ggtitle("Vader Reviews")
plot 

Statistics

# convert to binary results
reviews_50$afinn_binary[reviews_50$afinn > 0] <- "pos"
reviews_50$afinn_binary[reviews_50$afinn <= 0] <- "neg"

reviews_50$lsd_binary[reviews_50$lsd > 0] <- "pos"
reviews_50$lsd_binary[reviews_50$lsd <= 0] <- "neg"

reviews_50$vader_binary[reviews_50$vader > 0] <- "pos"
reviews_50$vader_binary[reviews_50$vader <= 0] <- "neg"

reviews_50$rating_binary[reviews_50$rating >= 3] <- "pos"
reviews_50$rating_binary[reviews_50$rating < 3] <- "neg"

# optionally: convert 0 = negative, 1 = positive
reviews_50[reviews_50 == "pos"] <- 1
reviews_50[reviews_50 == "neg"] <- 0
actual_values <- test$rating_binary
predict_values <- test$afinn_binary

# create confusion matrix 
confusion_matrix <- table(ACTUAL=actual_values, PREDICTED=predict_values)

# assign values from matrix to true/false positives/negatives
TN <- confusion_matrix[1]
FN <- confusion_matrix[2]
FP <- confusion_matrix[3]
TP <- confusion_matrix[4]

# calculate statistics
precision <- TP/(TP+FP)
accuracy <- (TP+TN)/(TP+TN+FP+FN)
recall <- TP/(TP+FN)
F1 <- (2*precision*recall)/(precision+recall)
get_statistics <- function(df) {
  statistics <- data.frame(matrix(ncol=4, nrow=0))
  x <- c("accuracy", "precision", "recall", "F1")
  colnames(statistics) <- x
  lex1 <- "afinn_binary"
  lex2 <- "lsd_binary"
  lex3 <- "vader_binary"
  gold <- "rating_binary"
  
  lexicons <- c(lex1,lex2,lex3)
  
  for(lex in lexicons){
    confusion_matrix <- table(ACTUAL=df[[gold]], PREDICTED=df[[lex]])
    TN <- confusion_matrix[1]
    FN <- confusion_matrix[2]
    FP <- confusion_matrix[3]
    TP <- confusion_matrix[4]
  
    # calculate statistics
    precision <- TP/(TP+FP)
    accuracy <- (TP+TN)/(TP+TN+FP+FN)
    recall <- TP/(TP+FN)
    F1 <- (2*precision*recall)/(precision+recall)
  
    # add to table
    output <- c(accuracy,precision, recall, F1)
    statistics[lex,] = rbind(statistics[[lex]], output)
    }
    
  return(statistics)
  
}

get_statistics(test)
LS0tCnRpdGxlOiAiQ29tcGFyaXNvbiBvZiBTZW50aW1lbnQgVG9vbHMgYWNyb3NzIERvbWFpbnMiCm91dHB1dDogaHRtbF9ub3RlYm9vawotLS0KCkhpbnQ6ClRyeSBleGVjdXRpbmcgdGhpcyBjaHVuayBieSBjbGlja2luZyB0aGUgKlJ1biogYnV0dG9uIHdpdGhpbiB0aGUgY2h1bmsgb3IgYnkgcGxhY2luZyB5b3VyIGN1cnNvciBpbnNpZGUgaXQgYW5kIHByZXNzaW5nICpDbWQrU2hpZnQrRW50ZXIqLiAKCmBgYHtyfQojIGxvYWQgcmVxdWlyZWQgbGlicmFyaWVzCgojIHRvIHVzZSBoYXJyeSBwb3R0ZXIgZGF0YXNldAojIGRldnRvb2xzOjppbnN0YWxsX2dpdGh1YigiYnJhZGxleWJvZWhta2UvaGFycnlwb3R0ZXIiKQojIGRldnRvb2xzOjppbnN0YWxsX2dpdGh1YigicXVhbnRlZGEvcXVhbnRlZGEuc2VudGltZW50IikKIyBkZXZ0b29sczo6aW5zdGFsbF9naXRodWIoInF1YW50ZWRhL3F1YW50ZWRhLmNvcnBvcmEiKQoKbGlicmFyeShxdWFudGVkYSkKbGlicmFyeShyZWFkdGV4dCkKbGlicmFyeShjb3JwdXMpCmxpYnJhcnkodGlkeXZlcnNlKQpsaWJyYXJ5KHN0cmluZ3IpCmxpYnJhcnkodGlkeXRleHQpCmxpYnJhcnkoaGFycnlwb3R0ZXIpCmxpYnJhcnkoZHBseXIpCmxpYnJhcnkocXVhbnRlZGEuc2VudGltZW50KQpsaWJyYXJ5KHZhZGVyKQoKCnJlcXVpcmUocXVhbnRlZGEpCnJlcXVpcmUocXVhbnRlZGEuY29ycG9yYSkKcmVxdWlyZShxdWFudGVkYS5zZW50aW1lbnQpCiNsaWJyYXJ5KCJxdWFudGVkYSIsIHdhcm4uY29uZmxpY3RzID0gRkFMU0UsIHF1aWV0bHkgPSBUUlVFKQpgYGAKCmBgYHtyfQphZmlubjIgPC0gZGF0YV9kaWN0aW9uYXJ5X0FGSU5OCgpgYGAKCiMgSGFycnkgUG90dGVyIC0gRGF0YXNldApgYGB7cn0KIyBsb2FkIGhhcnJ5IHBvdHRlciBkYXRhc2V0IAp0aXRsZXMgPC0gYygiUGhpbG9zb3BoZXIncyBTdG9uZSIsICJDaGFtYmVyIG9mIFNlY3JldHMiLCAiUHJpc29uZXIgb2YgQXprYWJhbiIsCiAgICAgICAgICAgICJHb2JsZXQgb2YgRmlyZSIsICJPcmRlciBvZiB0aGUgUGhvZW5peCIsICJIYWxmLUJsb29kIFByaW5jZSIsCiAgICAgICAgICAgICJEZWF0aGx5IEhhbGxvd3MiKQoKYm9va3MgPC0gbGlzdChwaGlsb3NvcGhlcnNfc3RvbmUsIGNoYW1iZXJfb2Zfc2VjcmV0cywgcHJpc29uZXJfb2ZfYXprYWJhbiwKICAgICAgICAgICBnb2JsZXRfb2ZfZmlyZSwgb3JkZXJfb2ZfdGhlX3Bob2VuaXgsIGhhbGZfYmxvb2RfcHJpbmNlLAogICAgICAgICAgIGRlYXRobHlfaGFsbG93cykKICAKc2VyaWVzIDwtIHRpYmJsZSgpCgpmb3IoaSBpbiBzZXFfYWxvbmcodGl0bGVzKSkgewogICAgICAgIAogICAgICAgIGNsZWFuIDwtIHRpYmJsZShjaGFwdGVyID0gc2VxX2Fsb25nKGJvb2tzW1tpXV0pLAogICAgICAgICAgICAgICAgICAgICAgICB0ZXh0ID0gYm9va3NbW2ldXSkgJT4lCiAgICAgICAgICAgICAjdW5uZXN0X3Rva2Vucyh3b3JkLCB0ZXh0KSAlPiUKICAgICAgICAgICAgIG11dGF0ZShib29rID0gdGl0bGVzW2ldKSAlPiUKICAgICAgICAgICAgIHNlbGVjdChib29rLCBldmVyeXRoaW5nKCkpCgogICAgICAgIHNlcmllcyA8LSByYmluZChzZXJpZXMsIGNsZWFuKQp9CgpzZXJpZXMkYm9vayA8LSBmYWN0b3Ioc2VyaWVzJGJvb2ssIGxldmVscyA9IHJldih0aXRsZXMpKQoKc2VyaWVzCiNib29rX2dyb3VwcyA8LSBzZXJpZXMgJT4lIGdyb3VwX2J5KGJvb2ssIGNoYXB0ZXIpCiMgdG9rZW5pemUgaHAxCiNocDFfdG9rZW5pemVkIDwtIHRva2Vuc190b2xvd2VyKHRva2VucyhwaGlsb3NvcGhlcnNfc3RvbmUsIHJlbW92ZV9wdW5jdCA9IFRSVUUpKSAKYGBgCiMjIyBIYXJyeSBQb3R0ZXIgLSBBRklOTiBMZXhpY29uCiMgTGV4aWNvZGVyOiBIUApgYGB7cn0KIyBzZWxlY3Qgb25seSB0aGUgIm5lZ2F0aXZlIiBhbmQgInBvc2l0aXZlIiBjYXRlZ29yaWVzCiNkYXRhX2RpY3Rpb25hcnlfTFNEMjAxNV9wb3NfbmVnIDwtIGRhdGFfZGljdGlvbmFyeV9MU0QyMDE1WzE6Ml0KI2hwMV9sc2QgPC0gdG9rZW5zX2xvb2t1cChocDFfdG9rZW5pemVkLCBkaWN0aW9uYXJ5ID0gZGF0YV9kaWN0aW9uYXJ5X0xTRDIwMTVfcG9zX25lZykKCnBvbGFyaXR5KGRhdGFfZGljdGlvbmFyeV9MU0QyMDE1KSA8LSAKICBsaXN0KHBvcyA9IGMoInBvc2l0aXZlIiwgIm5lZ19uZWdhdGl2ZSIpLCBuZWcgPSBjKCJuZWdhdGl2ZSIsICJuZWdfcG9zaXRpdmUiKSkKCmhwMV9sc2QgPC0gdGV4dHN0YXRfcG9sYXJpdHkoaHAxX3Rva2VuaXplZCwgZGF0YV9kaWN0aW9uYXJ5X0xTRDIwMTUpCgpocDFfbHNkX3Rva2VucyA8LSB0b2tlbnNfbG9va3VwKGhwMV90b2tlbml6ZWQsIGRhdGFfZGljdGlvbmFyeV9MU0QyMDE1LCBuZXN0ZWRfc2NvcGUgPSAiZGljdGlvbmFyeSIsIGV4Y2x1c2l2ZSA9IEZBTFNFKQpocDFfbHNkLmRmIDwtIGFzLmRhdGEuZnJhbWUubWF0cml4KGhwMV9sc2QpCmhwMV9sc2QuZGYkY2hhcHRlciA8LSAxOm5yb3coaHAxX2xzZC5kZikKCnBsb3QgPC0gZ2dwbG90KGhwMV9sc2QsIGFlcyh4ID1ocDFfbHNkLmRmJGNoYXB0ZXIsIHk9c2VudGltZW50KSkgKwogICAgICAgICAgZ2VvbV9iYXIoYWxwaGEgPSAwLjgsIHN0YXQgPSAiaWRlbnRpdHkiLCBzaG93LmxlZ2VuZCA9IEZBTFNFKQpwbG90ICsgeWxpbSgtMS4wLCAxLjApICsgbGFicyh5PSJzZW50aW1lbnQiLCB4ID0gImNoYXB0ZXIiKSArIGdndGl0bGUoIkhQMSAtIExleGljb2RlciIpCmBgYAojIEFGSU5OOiBIUApgYGB7cn0KaHAxX2FmaW5uMiA8LSB0ZXh0c3RhdF92YWxlbmNlKGhwMV90b2tlbml6ZWQsIGFmaW5uMiwgbm9ybWFsaXplPSJkaWN0aW9uYXJ5IikKCmhwMV9hZmlubjIuZGYgPC0gYXMuZGF0YS5mcmFtZS5tYXRyaXgoaHAxX2FmaW5uMikKaHAxX2FmaW5uMi5kZiRjaGFwdGVyIDwtIDE6bnJvdyhocDFfYWZpbm4yLmRmKQoKcGxvdCA8LSBnZ3Bsb3QoaHAxX2FmaW5uMi5kZiwgYWVzKHggPWhwMV9hZmlubjIuZGYkY2hhcHRlciwgeT1zZW50aW1lbnQpKSArCiAgICAgICAgICBnZW9tX2JhcihhbHBoYSA9IDAuOCwgc3RhdCA9ICJpZGVudGl0eSIsIHNob3cubGVnZW5kID0gRkFMU0UpCnBsb3QgKyB5bGltKC0xLjAsIDEuMCkgKyBsYWJzKHk9InNlbnRpbWVudCIsIHggPSAiY2hhcHRlciIpICsgZ2d0aXRsZSgiSFAxIC0gQUZJTk4iKQpgYGAKIyBWQURFUjogSFAKYGBge3J9CmdldF92YWRlcihwaGlsb3NvcGhlcnNfc3RvbmVbMV0pCgpocDFfdmFkZXIgPC0gdmFkZXJfZGYocGhpbG9zb3BoZXJzX3N0b25lKQpocDFfdmFkZXIkY2hhcHRlciA8LSAxOm5yb3coaHAxX3ZhZGVyKQoKcGxvdCA8LSBnZ3Bsb3QoaHAxX3ZhZGVyLCBhZXMoeCA9Y2hhcHRlciwgeT1jb21wb3VuZCkpICsKICAgICAgICAgIGdlb21fYmFyKGFscGhhID0gMC44LCBzdGF0ID0gImlkZW50aXR5Iiwgc2hvdy5sZWdlbmQgPSBGQUxTRSkKcGxvdCArIHlsaW0oLTUuMCwgNS4wKSArIGxhYnMoeT0ic2VudGltZW50IiwgeCA9ICJjaGFwdGVyIikgKyBnZ3RpdGxlKCJIUDEgLSBWQURFUiIpCmBgYAojIFFVQU5URURBLlNFTlRJTUVOVAojIEFGSU5OOiBIUApgYGB7cn0KIyBXb3JrIHdpdGggcXVhbnRlZGEuc2VudGltZW50IG9uIEhQIGNvcnB1czoKIyBjb252ZXJ0IHRpYmJsZSB0byBkYXRhZnJhbWUKc2VyaWVzLmRmIDwtIGFzLmRhdGEuZnJhbWUoc2VyaWVzKQoKIyB0b2tlbml6ZSBib29rcwpzZXJpZXNfdG9rZW5pemVkIDwtIHNlcmllcy5kZiAlPiUKICB1bm5lc3RfdG9rZW5zKHRva2VucywgdGV4dCkKCiMgYXBwbHkgYWZpbm4gbGV4aWNvbgpzZXJpZXNfdG9rZW5pemVkJGFmaW5uMiA8LSB0ZXh0c3RhdF92YWxlbmNlKHNlcmllc190b2tlbml6ZWQkdG9rZW5zLCBhZmlubjIpJHNlbnRpbWVudAoKIyByZXBsYWNlIGFsbCAwIHZhbHVlcyB3aXRoIG5hCnNlcmllc190b2tlbml6ZWRbc2VyaWVzX3Rva2VuaXplZCA9PSAwXSA8LSBOQQoKc2VyaWVzX3Rva2VuaXplZCAlPiUKICBncm91cF9ieShib29rLCBjaGFwdGVyKSAlPiUgIyBncm91cCBkZiBieSBib29rIGFuZCBjaGFwdGVyIHRvIGdldCBzZW50aW1lbnQgcGVyIGNoYXB0ZXIKICBzdW1tYXJpc2Uoc2VudGltZW50ID0gbWVhbihhZmlubjIsIG5hLnJtID0gVFJVRSkpICU+JSAjIGNhbGN1bGF0ZSBtZWFuIHcvbyByZWdhcmRpbmcgbmEgdmFsdWVzCiAgbXV0YXRlKG1ldGhvZCA9ICJBRklOTiIpICU+JSAjIGFkZCBjb2x1bW4gd2l0aCBtZXRob2QgCiAgICAgICAgZ2dwbG90KGFlcyhjaGFwdGVyLCBzZW50aW1lbnQsIGZpbGwgPSBib29rKSkgKyAjIHBsb3Qgc2VudGltZW50IG9mIGJvb2tzCiAgICAgICAgICBnZW9tX2JhcihhbHBoYSA9IDAuOCwgc3RhdCA9ICJpZGVudGl0eSIsIHNob3cubGVnZW5kID0gRkFMU0UpICsKICAgICAgICAgIGZhY2V0X3dyYXAofiBib29rLCBuY29sID0gMiwgc2NhbGVzID0gImZyZWVfeCIpICsKICAgICAgICAgIGdndGl0bGUoIkFGSU5OIEhQIikKYGBgCiMgTGV4aWNvZGVyOiBIUCAgCmBgYHtyfQojIFdvcmsgd2l0aCBxdWFudGVkYS5zZW50aW1lbnQgb24gSFAgY29ycHVzOgojIGFwcGx5IGxleGljb2RlciBsZXhpY29uCnNlcmllcyRsc2QgPC0gdGV4dHN0YXRfcG9sYXJpdHkodG9rZW5zKHNlcmllcyR0ZXh0KSwgZGF0YV9kaWN0aW9uYXJ5X0xTRDIwMTUpJHNlbnRpbWVudCAKCiNzZXJpZXMuZGYgPC0gYXMuZGF0YS5mcmFtZShzZXJpZXMpCgpwbG90IDwtIGdncGxvdChzZXJpZXMsIGFlcyhjaGFwdGVyLCBsc2QsIGZpbGwgPSBib29rKSkgKyAjIHBsb3Qgc2VudGltZW50IG9mIGJvb2tzCiAgICAgICAgICBnZW9tX2JhcihhbHBoYSA9IDAuOCwgc3RhdCA9ICJpZGVudGl0eSIsIHNob3cubGVnZW5kID0gRkFMU0UpICsKICAgICAgICAgIGZhY2V0X3dyYXAofiBib29rLCBuY29sID0gMiwgc2NhbGVzID0gImZyZWVfeCIpICsKICAgICAgICAgIGdndGl0bGUoIkxleGljb2RlciBIUCIpCnBsb3QgCmBgYAoKIyBWYWRlcjogSFAKYGBge3J9CiMgYXBwbHkgdmFkZXIgbGV4aWNvbiB0byBhbGwgSFAgYm9va3MKc2VyaWVzJHZhZGVyIDwtIHZhZGVyX2RmKHNlcmllcyR0ZXh0KSRjb21wb3VuZAoKI3Nlcmllcy5kZiA8LSBhcy5kYXRhLmZyYW1lKHNlcmllcykKCnBsb3QgPC0gZ2dwbG90KHNlcmllcywgYWVzKGNoYXB0ZXIsIGxzZCwgZmlsbCA9IGJvb2spKSArICMgcGxvdCBzZW50aW1lbnQgb2YgYm9va3MKICAgICAgICAgIGdlb21fYmFyKGFscGhhID0gMC44LCBzdGF0ID0gImlkZW50aXR5Iiwgc2hvdy5sZWdlbmQgPSBGQUxTRSkgKwogICAgICAgICAgZmFjZXRfd3JhcCh+IGJvb2ssIG5jb2wgPSAyLCBzY2FsZXMgPSAiZnJlZV94IikgKwogICAgICAgICAgZ2d0aXRsZSgiVkFERVIgSFAiKQpwbG90IApgYGAKCiMgUkVWSUVXUyBEQVRBU0VUCmBgYHtyfQojIGxvYWQgZGF0YXNldApyZXZpZXdzIDwtIHJlYWR0ZXh0KCJkYXRhc2V0cy9nb29kcmVhZHNfcmV2aWV3c19jaGlsZHJlbl8yLmpzb24iLCB0ZXh0X2ZpZWxkID0gInJldmlld190ZXh0IikKCiMgY29udmVydCB0byBkYXRhZnJhbWUKcmV2aWV3cy5kZiA8LSBhcy5kYXRhLmZyYW1lKHJldmlld3MpCgojIGFkZCBkb2NfaWQgKGkuZS4gYWNjb3JkaW5nIHRvIGluZGV4KQpyZXZpZXdzLmRmJGRvY19pZCA8LSAxOm5yb3cocmV2aWV3cy5kZikKYGBgCgojIyMgU2FtcGxlIERhdGFzZXQKYGBge3J9CiMgZ2V0IHJhbmRvbSBzYW1wbGUgb2YgNTAgcmV2aWV3cyAKcmV2aWV3c19zYW1wbGUgPC0gcmV2aWV3cy5kZltzYW1wbGUoMTpucm93KHJldmlld3MuZGYpLCA1MCwKICAgcmVwbGFjZT1GQUxTRSksXQoKIyBnZXQgZmlyc3QgNTAgcm93cyBvZiBkYXRhIApyZXZpZXdzXzUwIDwtIGhlYWQocmV2aWV3cy5kZiw1MCkKcmV2aWV3c181MCA9IHN1YnNldChyZXZpZXdzXzUwLCBzZWxlY3QgPSBjKGRvY19pZCx0ZXh0LHJhdGluZykpCmBgYAojIyMgR2V0IFRyYW5zbGF0aW9ucyBvZiBEYXRhc2V0IApgYGB7cn0KIyBlaXRoZXIgdmlhIGNvcnB1cyAKcmV2aWV3cy5jb3JwdXMgPC0gY29ycHVzKHJldmlld3MpCmRvY3ZhcnMocmV2aWV3cy5jb3JwdXMsICJsYW5ndWFnZSIpIDwtIHRleHRjYXQocmV2aWV3cy5jb3JwdXMpCnJldmlld3NfZW4gPC0gY29ycHVzX3N1YnNldChyZXZpZXdzLmNvcnB1cywgbGFuZ3VhZ2UgPT0gImVuZ2xpc2giLCBkcm9wX2RvY2lkID0gVFJVRSkKCiMgb3IgdmlhIGRhdGFmcmFtZSBsb2dpYwpyZXZpZXdzLmRmJGxhbmd1YWdlIDwtIHRleHRjYXQocmV2aWV3cy5kZiR0ZXh0KQpgYGAKCiMjIyBTZW50aW1lbnQgQW5hbHlzaXMgb24gUmV2aWV3cyBEYXRhc2V0CgojIyMjIEFGSU5OCmBgYHtyfQojIEFmaW5uCiMgdG9rZW5pemUgCnJldmlld3NfdG9rZW5pemVkIDwtIHJldmlld3NfNTAgJT4lCiAgdW5uZXN0X3Rva2Vucyh0b2tlbnMsIHRleHQpCgojIGFwcGx5IGFmaW5uIGxleGljb24KcmV2aWV3c190b2tlbml6ZWQkYWZpbm4yIDwtIHRleHRzdGF0X3ZhbGVuY2UocmV2aWV3c190b2tlbml6ZWQkdG9rZW5zLCBhZmlubjIpJHNlbnRpbWVudAoKIyByZXBsYWNlIGFsbCAwIHZhbHVlcyB3aXRoIG5hCnJldmlld3NfdG9rZW5pemVkW3Jldmlld3NfdG9rZW5pemVkID09IDBdIDwtIE5BCgojIGNhbGN1bGF0ZSBtZWFuIHNjb3JlcyBmb3IgdG9rZW5zIHBlciBkb2MKYWZpbm5fc2NvcmVzIDwtIHJldmlld3NfdG9rZW5pemVkICU+JQogIGdyb3VwX2J5KGRvY19pZCkgJT4lICMgZ3JvdXAgZGYgYnkgZG9jX2lkIHRvIGdldCBtZWFuIHNlbnRpbWVudCBzY29yZQogIHN1bW1hcmlzZSh0b3RhbCA9IG1lYW4oYWZpbm4yLCBuYS5ybSA9IFRSVUUpKSAjJT4lICMgY2FsY3VsYXRlIG1lYW4gdy9vIHJlZ2FyZGluZyBuYSB2YWx1ZXMKCiMgYWRkIGFmaW5uIHNjb3JlcyB0byBkZiAKcmV2aWV3c181MCRhZmlubiA8LSBhZmlubl9zY29yZXMkdG90YWwKCiMgZGlmZmVyZW50IHZlcnNpb24gdG8gZ2V0IHBsb3QgCnJldmlld3NfdG9rZW5pemVkICU+JQogIGdyb3VwX2J5KGRvY19pZCkgJT4lICMgZ3JvdXAgZGYgYnkgYm9vayBhbmQgY2hhcHRlciB0byBnZXQgc2VudGltZW50IHBlciBjaGFwdGVyCiAgI3Jldmlld3NfdG9rZW5pemVkJHNlbnRpbWVudCA9IG1lYW4oYWZpbm4yLCBuYS5ybSA9IFRSVUUpICU+JQogIHN1bW1hcmlzZShzZW50aW1lbnQgPSBtZWFuKGFmaW5uMiwgbmEucm0gPSBUUlVFKSkgJT4lICMgY2FsY3VsYXRlIG1lYW4gdy9vIHJlZ2FyZGluZyBuYSB2YWx1ZXMKICBtdXRhdGUobWV0aG9kID0gIkFGSU5OIikgJT4lICMgYWRkIGNvbHVtbiB3aXRoIG1ldGhvZCAKICAgICAgICBnZ3Bsb3QoYWVzKGRvY19pZCwgc2VudGltZW50LCBmaWxsID0gZG9jX2lkKSkgKyAjIHBsb3Qgc2VudGltZW50IG9mIGJvb2tzCiAgICAgICAgICBnZW9tX2JhcihhbHBoYSA9IDAuOCwgc3RhdCA9ICJpZGVudGl0eSIsIHNob3cubGVnZW5kID0gRkFMU0UpICsKICAgICAgICAgICNmYWNldF93cmFwKH4gZG9jX2lkLCBuY29sID0gMiwgc2NhbGVzID0gImZyZWVfeCIpICsKICAgICAgICAgIGdndGl0bGUoIkFGSU5OIFJldmlld3MiKQpgYGAKIyMjIyBMRVhJQ09ERVIKYGBge3J9CiMgYXBwbHkgbGV4aWNvZGVyIGxleGljb24KcmV2aWV3c181MCRsc2QgPC0gdGV4dHN0YXRfcG9sYXJpdHkodG9rZW5zKHJldmlld3NfNTAkdGV4dCksIGRhdGFfZGljdGlvbmFyeV9MU0QyMDE1KSRzZW50aW1lbnQgCiNzZXJpZXMuZGYgPC0gYXMuZGF0YS5mcmFtZShzZXJpZXMpCgpwbG90IDwtIGdncGxvdChyZXZpZXdzXzUwLCBhZXMoZG9jX2lkLCBsc2QsIGZpbGwgPSBkb2NfaWQpKSArICMgcGxvdCBzZW50aW1lbnQgb2YgYm9va3MKICAgICAgICAgIGdlb21fYmFyKGFscGhhID0gMC44LCBzdGF0ID0gImlkZW50aXR5Iiwgc2hvdy5sZWdlbmQgPSBGQUxTRSkgKwogICAgICAgICAgI2ZhY2V0X3dyYXAofiBkb2NfaWQsIG5jb2wgPSAyLCBzY2FsZXMgPSAiZnJlZV94IikgKwogICAgICAgICAgZ2d0aXRsZSgiTGV4aWNvZGVyIFJldmlld3MiKQpwbG90IApgYGAKIyMjIyBWYWRlcgpgYGB7cn0KcmV2aWV3c181MCR2YWRlciA8LSB2YWRlcl9kZihyZXZpZXdzXzUwJHRleHQpJGNvbXBvdW5kCgpwbG90IDwtIGdncGxvdChyZXZpZXdzXzUwLCBhZXMoZG9jX2lkLCB2YWRlciwgZmlsbCA9IGRvY19pZCkpICsgIyBwbG90IHNlbnRpbWVudCBvZiBib29rcwogICAgICAgICAgZ2VvbV9iYXIoYWxwaGEgPSAwLjgsIHN0YXQgPSAiaWRlbnRpdHkiLCBzaG93LmxlZ2VuZCA9IEZBTFNFKSArCiAgICAgICAgICAjZmFjZXRfd3JhcCh+IGRvY19pZCwgbmNvbCA9IDIsIHNjYWxlcyA9ICJmcmVlX3giKSArCiAgICAgICAgICBnZ3RpdGxlKCJWYWRlciBSZXZpZXdzIikKcGxvdCAKYGBgCiMgU3RhdGlzdGljcyAKYGBge3J9CiMgY29udmVydCB0byBiaW5hcnkgcmVzdWx0cwpyZXZpZXdzXzUwJGFmaW5uX2JpbmFyeVtyZXZpZXdzXzUwJGFmaW5uID4gMF0gPC0gInBvcyIKcmV2aWV3c181MCRhZmlubl9iaW5hcnlbcmV2aWV3c181MCRhZmlubiA8PSAwXSA8LSAibmVnIgoKcmV2aWV3c181MCRsc2RfYmluYXJ5W3Jldmlld3NfNTAkbHNkID4gMF0gPC0gInBvcyIKcmV2aWV3c181MCRsc2RfYmluYXJ5W3Jldmlld3NfNTAkbHNkIDw9IDBdIDwtICJuZWciCgpyZXZpZXdzXzUwJHZhZGVyX2JpbmFyeVtyZXZpZXdzXzUwJHZhZGVyID4gMF0gPC0gInBvcyIKcmV2aWV3c181MCR2YWRlcl9iaW5hcnlbcmV2aWV3c181MCR2YWRlciA8PSAwXSA8LSAibmVnIgoKcmV2aWV3c181MCRyYXRpbmdfYmluYXJ5W3Jldmlld3NfNTAkcmF0aW5nID49IDNdIDwtICJwb3MiCnJldmlld3NfNTAkcmF0aW5nX2JpbmFyeVtyZXZpZXdzXzUwJHJhdGluZyA8IDNdIDwtICJuZWciCgojIG9wdGlvbmFsbHk6IGNvbnZlcnQgMCA9IG5lZ2F0aXZlLCAxID0gcG9zaXRpdmUKcmV2aWV3c181MFtyZXZpZXdzXzUwID09ICJwb3MiXSA8LSAxCnJldmlld3NfNTBbcmV2aWV3c181MCA9PSAibmVnIl0gPC0gMApgYGAKCmBgYHtyfQphY3R1YWxfdmFsdWVzIDwtIHRlc3QkcmF0aW5nX2JpbmFyeQpwcmVkaWN0X3ZhbHVlcyA8LSB0ZXN0JGFmaW5uX2JpbmFyeQoKIyBjcmVhdGUgY29uZnVzaW9uIG1hdHJpeCAKY29uZnVzaW9uX21hdHJpeCA8LSB0YWJsZShBQ1RVQUw9YWN0dWFsX3ZhbHVlcywgUFJFRElDVEVEPXByZWRpY3RfdmFsdWVzKQoKIyBhc3NpZ24gdmFsdWVzIGZyb20gbWF0cml4IHRvIHRydWUvZmFsc2UgcG9zaXRpdmVzL25lZ2F0aXZlcwpUTiA8LSBjb25mdXNpb25fbWF0cml4WzFdCkZOIDwtIGNvbmZ1c2lvbl9tYXRyaXhbMl0KRlAgPC0gY29uZnVzaW9uX21hdHJpeFszXQpUUCA8LSBjb25mdXNpb25fbWF0cml4WzRdCgojIGNhbGN1bGF0ZSBzdGF0aXN0aWNzCnByZWNpc2lvbiA8LSBUUC8oVFArRlApCmFjY3VyYWN5IDwtIChUUCtUTikvKFRQK1ROK0ZQK0ZOKQpyZWNhbGwgPC0gVFAvKFRQK0ZOKQpGMSA8LSAoMipwcmVjaXNpb24qcmVjYWxsKS8ocHJlY2lzaW9uK3JlY2FsbCkKYGBgCgpgYGB7cn0KZ2V0X3N0YXRpc3RpY3MgPC0gZnVuY3Rpb24oZGYpIHsKICBzdGF0aXN0aWNzIDwtIGRhdGEuZnJhbWUobWF0cml4KG5jb2w9NCwgbnJvdz0wKSkKICB4IDwtIGMoImFjY3VyYWN5IiwgInByZWNpc2lvbiIsICJyZWNhbGwiLCAiRjEiKQogIGNvbG5hbWVzKHN0YXRpc3RpY3MpIDwtIHgKICBsZXgxIDwtICJhZmlubl9iaW5hcnkiCiAgbGV4MiA8LSAibHNkX2JpbmFyeSIKICBsZXgzIDwtICJ2YWRlcl9iaW5hcnkiCiAgZ29sZCA8LSAicmF0aW5nX2JpbmFyeSIKICAKICBsZXhpY29ucyA8LSBjKGxleDEsbGV4MixsZXgzKQogIAogIGZvcihsZXggaW4gbGV4aWNvbnMpewogICAgY29uZnVzaW9uX21hdHJpeCA8LSB0YWJsZShBQ1RVQUw9ZGZbW2dvbGRdXSwgUFJFRElDVEVEPWRmW1tsZXhdXSkKICAgIFROIDwtIGNvbmZ1c2lvbl9tYXRyaXhbMV0KICAgIEZOIDwtIGNvbmZ1c2lvbl9tYXRyaXhbMl0KICAgIEZQIDwtIGNvbmZ1c2lvbl9tYXRyaXhbM10KICAgIFRQIDwtIGNvbmZ1c2lvbl9tYXRyaXhbNF0KICAKICAgICMgY2FsY3VsYXRlIHN0YXRpc3RpY3MKICAgIHByZWNpc2lvbiA8LSBUUC8oVFArRlApCiAgICBhY2N1cmFjeSA8LSAoVFArVE4pLyhUUCtUTitGUCtGTikKICAgIHJlY2FsbCA8LSBUUC8oVFArRk4pCiAgICBGMSA8LSAoMipwcmVjaXNpb24qcmVjYWxsKS8ocHJlY2lzaW9uK3JlY2FsbCkKICAKICAgICMgYWRkIHRvIHRhYmxlCiAgICBvdXRwdXQgPC0gYyhhY2N1cmFjeSxwcmVjaXNpb24sIHJlY2FsbCwgRjEpCiAgICBzdGF0aXN0aWNzW2xleCxdID0gcmJpbmQoc3RhdGlzdGljc1tbbGV4XV0sIG91dHB1dCkKICAgIH0KICAgIAogIHJldHVybihzdGF0aXN0aWNzKQogIAp9CgpnZXRfc3RhdGlzdGljcyh0ZXN0KQpgYGAKCgoKCgoKCg==